﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MetaCollection.Framework;
using MetaCollection.ContentService.Http;
using MetaCollection.Framework.Model;
using System.Globalization;

namespace MetaCollection.ContentService.WebDav.Methods
{
	/// <summary>
	/// Handles a PUT request as defined in RFC4918, RFC2518 and RFC2616.
	/// 
	/// A PUT performed on an existing resource replaces the GET response entity of the resource. Properties defined
	/// on the resource may be recomputed during PUT processing but are not otherwise affected. For example, if a 
	/// server recognizes the content type of the request body, it may be able to automatically extract information
	/// that could be profitably exposed as properties.
	/// 
	/// A PUT that would result in the creation of a resource without an appropriately scoped parent collection MUST
	/// fail with a 409 (Conflict).
	///
	/// A PUT request allows a client to indicate what media type an entity body has, and whether it should change
	/// if overwritten. Thus, a client SHOULD provide a Content-Type for a new resource if any is known. If the client
	/// does not provide a Content-Type for a new resource, the server MAY create a resource with no Content-Type
	/// assigned, or it MAY attempt to assign a Content-Type.
	///
	/// Note that although a recipient ought generally to treat metadata supplied with an HTTP request as authoritative,
	/// in practice there's no guarantee that a server will accept client-supplied metadata (e.g., any request header
	/// beginning with "Content-"). Many servers do not allow configuring the Content-Type on a per-resource basis in
	/// the first place. Thus, clients can't always rely on the ability to directly influence the content type by
	/// including a Content-Type request header.
	/// </summary>
	/// <remarks>
	/// A Put request creating a collection will result in a 405 (Method Not Allowed).  Clients attempting to create
	/// a collection should use MKCOL instead.
	/// 
	/// If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response.
	/// If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent
	/// to indicate successful completion of the request. If the resource could not be created or modified with the
	/// Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem. The 
	/// recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand 
	/// or implement and MUST return a 501 (Not Implemented) response in such cases. 
	/// </remarks>
	public static class Put
	{
		public static void Respond(DavContext context, IRequestHandler handler)
		{
			Uri itemUri = context.Request.Url.ToCollectionUri();
			IItemInstance item = context.Item as IItemInstance;
			
			if (context.Request.ContentType.Equals("application/x-folder", System.StringComparison.OrdinalIgnoreCase))
				throw new ClientException(ClientErrorStatus.MethodNotAllowed, "Method not allowed.  Use MKCol to create collections.");

			if (item == null)
			{
				// Item truly does not exist. Create it so we can store the posted resource under it.
				context.Response.SetStatus((int)SuccessStatus.Created, "Created");
				string parentUriStr = itemUri.ToString().TrimEnd('/');
				parentUriStr.Substring(0, parentUriStr.LastIndexOf('/', 7)); // skip the col:// part of uri
				Uri parentUri = new Uri(parentUriStr);
				// uri for creation must point to parent.  TODO : there must be a better way to strip the last segment off to find the parent folder.
				item = CreateItem(context, handler, parentUri);
			}
			else
			{
				context.Response.SetStatus((int)SuccessStatus.NoContent, "Item updated successfuly.");

				// If the item is currently published or the user does not own a lock on the item then we need to make a new version
				if (!LockProperty.CurrentUserOwnsLock(item) || item.Published)
				{
					item.LeftVersion = item.Version;
					item.Version = 0;
					item.Published = false;
				}
				// update the lock on the item so our timeout is moved out into the future
				item.Properties = item.Properties.PatchProperty(Properties.CONTENTSERVICE_LOCK_PROPERTY_NAME, LockProperty.GetNewLockForCurrentUser()); // lock the item to the user.
				item = handler.ItemRequest(context.User, RequestTypes.SetItem, item.Href, item).FirstOrDefault();
			}

			// TODO : support ranges.
			// if at this point we have an item created/loaded then we will want to read off the posted data and perform
			// setresource commands on the item.  If there are content-ranges then we will need to perform getresource
			// and setresource commands to allow data to be manipulated as requested.

			context.Response.SetItemHeaders(item, HttpCacheability.NoCache);
			handler.ItemRequest(context.User, RequestTypes.SetResource | RequestTypes.VersionSpecific, itemUri, context.Request.InputStream, item, context.Request.Form, item.Version, LoadOptions.ItemCreateReference);
			context.Response.Headers.Add("Location", String.Format(CultureInfo.InvariantCulture, "{0}?ver={1}", item.Href.ToWebsiteUri().AbsoluteUri, item.Version));
		}

		private static IItemInstance CreateItem(DavContext context, IRequestHandler handler, Uri itemUri)
		{
			Uri parentUri = new Uri(itemUri.AbsoluteUri.Substring(0, itemUri.AbsoluteUri.LastIndexOf('/')));
			IItemInstance parentItem = handler.ItemRequest(context.User, RequestTypes.GetItem, parentUri, LoadOptions.ItemReference).FirstOrDefault();
			// if the parent does not exist then fail with a 409 Conflict.
			if (parentItem == null)
				throw new ClientException(ClientErrorStatus.Conflict, "Parent Folder Must Exist");

			// TODO: Valid name regular expression (A-Za-z0-9\!\*\'\(\)\,\$\-_\.\+)
			ItemInstance newItem = new ItemInstance();

			string name = itemUri.AbsoluteUri.Substring(itemUri.AbsoluteUri.LastIndexOf('/') + 1);

			newItem.Name = name;
			try
			{
				newItem.Id = new Guid(name);
			}
			catch (FormatException) { }
			// an absolute URI may contain a query string... we don't want that.
			if (newItem.Name.Contains('?'))
				newItem.Name = newItem.Name.Substring(newItem.Name.IndexOf('?'));
			newItem.Name = Uri.UnescapeDataString(newItem.Name);

			newItem.ParentId = parentItem.Id;
			newItem.Specification = context.Request.SpecificationId.HasValue ? context.Request.SpecificationId.Value : parentItem.Specification;
			newItem.MimeType = context.Request.ContentType;
			newItem.CreatedBy = context.User.Identity.Name;
			newItem.LastModified = DateTime.Now;
			newItem.Published = true; // this is a fix for webdav clients that create items and need at least one published version to function properly.
			newItem.Properties = newItem.Properties.PatchProperty(Properties.CONTENTSERVICE_LOCK_PROPERTY_NAME, LockProperty.GetNewLockForCurrentUser()); // lock the item to the user.
			return handler.ItemRequest(context.User, RequestTypes.SetItem, parentUri, newItem, LoadOptions.ItemCreateReference | LoadOptions.Properties | LoadOptions.LastModified).FirstOrDefault();
		}
	}
}
